using System;
using System.Text;
using CLELCore.Executable;

namespace CLELCore.Assembly.Generator.ValidAssemblyGenerator
{
	public class CLELValidAssemblyGenerator : ICLELGenerator
	{
		private CLELAssembly _asm;
		private ICLELWriter _writer;
		private CLELLogger _log;
		private int _file_offset;
		private int _rva;

		public CLELValidAssemblyGenerator(ICLELWriter writer,CLELLogger log)
		{
			_writer = writer;
			_log = log;
			_file_offset = 0;
		}

		public void Write(CLELAssembly asm)
		{
			_asm = asm;
			WriteDOSHeader();
			//PE\0\0
			byte[] signature = _asm.Signature;
			if(signature != null && signature.Length == 4 && 
				signature[0] == 0x50 && signature[1] == 0x45 && 
				signature[2] == 0x0 && signature[3] == 0x0)
			{
				WritePEHeader();
			}
			else
			{
				throw new CLELWriterException("Error unknown signature: "+ ASCIIEncoding.ASCII.GetString(signature));
			}

			WriteStandardFields();
			WriteNTSpecific();
			WriteDataDirectories();
			WriteSectionHeaders();
			WriteSections();
		}
		
		private void WriteDOSHeader()
		{
			_log.WriteLine("CLEL - using CLELValidAssemblyGenerator");
			_log.WriteLine("Writing - " + _writer.Path);
			_log.WriteLine(DateTime.Now.ToString()+"\n");

			DOSHeader dosheader = _asm.DOSHeader;
			short mn = dosheader.MagicNumber;
			byte m1 = Bits.ByteFromShort(1,mn);
			byte m2 = Bits.ByteFromShort(0,mn);
			_writer.WriteByte(m1);
			_writer.WriteByte(m2);
			_writer.WriteShort(dosheader.LastPageOfFile);
			_writer.WriteShort(dosheader.PagesInFile);
			_writer.WriteShort(dosheader.Relocations);
			_writer.WriteShort(dosheader.SizeOfHeader);
			_writer.WriteShort(dosheader.MinParagraphs);
			_writer.WriteShort(dosheader.MaxParagraphs);
			_writer.WriteShort(dosheader.InitialSSValue);
			_writer.WriteShort(dosheader.InitialSPValue);
			_writer.WriteShort(dosheader.Checksum);
			_writer.WriteShort(dosheader.InitialIPValue);
			_writer.WriteShort(dosheader.InitialCSValue);
			_writer.WriteShort(dosheader.RelocationsTable);
			_writer.WriteShort(dosheader.OverlayNumber);
			_writer.Write(dosheader.Reserved,0,dosheader.Reserved.Length);
			_writer.WriteShort(dosheader.OEMIdentifier);
			_writer.WriteShort(dosheader.OEMInformation);
			_writer.Write(dosheader.Reserved2,0,dosheader.Reserved2.Length);
			_writer.WriteInt(dosheader.SignatureOffset);
			_writer.Write(dosheader.DOSStub,0,dosheader.DOSStub.Length);
			
			_writer.Write(_asm.Signature,0,_asm.Signature.Length);

			int length = 36 + dosheader.Reserved.Length + dosheader.Reserved2.Length + dosheader.DOSStub.Length;			
			_file_offset += length;
			_file_offset += _asm.Signature.Length;
		}

		private void WritePEHeader()
		{
			PEHeader peheader = _asm.PEHeader;
		
			_writer.WriteShort(peheader.MachineType);
			_writer.WriteShort(peheader.NumberOfSections);
			_writer.WriteInt(peheader.TimeDateStamp);
			_writer.WriteInt(peheader.PointerToSymbolTable);
			_writer.WriteInt(peheader.NumberOfSymbols);
			_writer.WriteShort(peheader.OptionalHeaderSize);
			_writer.WriteShort(peheader.Characteristics);
			_file_offset += PEHeader.PEHEADER_SIZE;
		}

		private void WriteStandardFields()
		{
			StandardFields standardfields = _asm.StandardFields;

			short magic = standardfields.Magic;
			byte m1 = Bits.ByteFromShort(0,magic);
			byte m2 = Bits.ByteFromShort(1,magic);
			_writer.WriteByte(m1);
			_writer.WriteByte(m2);
			_writer.WriteByte(standardfields.MajorLinkerVersion);
			_writer.WriteByte(standardfields.MinorLinkerVersion);
			_writer.WriteInt(standardfields.SizeOfCode);
			_writer.WriteInt(standardfields.SizeOfInitalizedData);
			_writer.WriteInt(standardfields.SizeOfUninitalizedData);
			_writer.WriteInt(standardfields.AddressOfEntryPoint);
			_writer.WriteInt(standardfields.BaseOfCode);
			if(magic == StandardFields.PE32_MAGIC)
			{
				_writer.WriteInt(standardfields.BaseOfData);
			}
			
			if(magic == StandardFields.PE32_MAGIC)
				_file_offset += StandardFields.PE32_STANDARD_FIELDS_SIZE;
			else if(magic == StandardFields.PE32PLUS_MAGIC)
				_file_offset += StandardFields.PE32PLUS_STANDARD_FIELDS_SIZE;
		}

		private void WriteNTSpecific()
		{
			NTSpecific ntspecific = _asm.NTSpecific;

			short magic = _asm.StandardFields.Magic;
			
			int itemp;
			if(magic == StandardFields.PE32_MAGIC)
			{
				itemp = (int)ntspecific.ImageBase;
				_writer.WriteInt(itemp);
			}
			else
			{
				_writer.WriteLong(ntspecific.ImageBase);
			}

			_writer.WriteInt(ntspecific.SectionAlignment);
			_writer.WriteInt(ntspecific.FileAlignment);
			_writer.WriteShort(ntspecific.MajorOSVersion);
			_writer.WriteShort(ntspecific.MinorOSVersion);
			_writer.WriteShort(ntspecific.MajorImageVersion);
			_writer.WriteShort(ntspecific.MinorImageVersion);
			_writer.WriteShort(ntspecific.MajorSubSysVersion);
			_writer.WriteShort(ntspecific.MinorSubSysVersion);
			_writer.WriteInt(ntspecific.Win32VersionValue);
			_writer.WriteInt(ntspecific.SizeOfImage);
			_writer.WriteInt(ntspecific.SizeOfHeaders);
			_writer.WriteInt(ntspecific.Checksum);
			_writer.WriteShort(ntspecific.Subsystem);
			_writer.WriteShort(ntspecific.DLLCharacteristics);
				
			if(magic == StandardFields.PE32_MAGIC)
			{
				itemp = (int)ntspecific.SizeOfStackReserve;
				_writer.WriteInt(itemp);
				itemp = (int)ntspecific.SizeOfStackCommit;
				_writer.WriteInt(itemp);
				itemp = (int)ntspecific.SizeOfHeapReserve;
				_writer.WriteInt(itemp);
				itemp = (int)ntspecific.SizeOfHeapCommit;
				_writer.WriteInt(itemp);
			}
			else
			{
				_writer.WriteLong(ntspecific.SizeOfStackReserve);
				_writer.WriteLong(ntspecific.SizeOfStackCommit);
				_writer.WriteLong(ntspecific.SizeOfHeapReserve);
				_writer.WriteLong(ntspecific.SizeOfHeapCommit);
			}

			_writer.WriteInt(ntspecific.LoaderFlags);
			_writer.WriteInt(ntspecific.NumberOfDataDirectories);
			
			if(magic == StandardFields.PE32_MAGIC)
				_file_offset += NTSpecific.PE32_NT_SPECIFIC_SIZE;
			else if(magic == StandardFields.PE32PLUS_MAGIC)
				_file_offset += NTSpecific.PE32PLUS_NT_SPECIFIC_SIZE;
		}

		private void WriteDataDirectories()
		{
			DataDirectories datadirectories = _asm.DataDirectories;

			int num_data_directories = _asm.NTSpecific.NumberOfDataDirectories;
			int i;
			int va,size;
			DataDirectory dd;
			for(i = 0;i < num_data_directories;i++)
			{
				dd = datadirectories[i];
				va = dd.VirtualAddress;
				size = dd.Size;
				_writer.WriteInt(va);
				_writer.WriteInt(size);
			}
			_file_offset += (num_data_directories * 8);
		}

		private void WriteSectionHeaders()
		{
			SectionHeader[] sectionheaders = _asm.SectionHeaders;

			int num_sections = _asm.PEHeader.NumberOfSections;
			int i;
			SectionHeader sh;
			for(i = 0;i < num_sections;i++)
			{
				sh = sectionheaders[i];
				
				_writer.Write(sh.Name,0,sh.Name.Length);
				_writer.WriteInt(sh.VirtualSize);
				_writer.WriteInt(sh.VirtualAddress);
				_writer.WriteInt(sh.SizeOfRawData);
				_writer.WriteInt(sh.PointerToRawData);
				_writer.WriteInt(sh.PointerToRelocations);
				_writer.WriteInt(sh.PointerToLineNumbers);
				_writer.WriteShort(sh.NumberOfRelocations);
				_writer.WriteShort(sh.NumberOfLineNumbers);
				_writer.WriteInt(sh.Characteristics);
			}

			int len = num_sections*SectionHeader.SECTION_HEADER_SIZE;
			_file_offset += len;

			WriteHeadersPadding();
		}

		private void WriteHeadersPadding()
		{
			int padding_length = _asm.NTSpecific.SizeOfHeaders-_file_offset;
			if(padding_length > 0)
			{
				int i;
				for(i = 0;i < padding_length;i++)
					_writer.WriteByte(0);
				_file_offset += padding_length;
			}
		}

		private void WriteSections()
		{
			int i,j,k;
			int next;
			SectionHeader sh;
			SectionHeader [] sectionheaders = _asm.SectionHeaders;
			int len = sectionheaders.Length;
			//the spec does not say that the section headers are in order
			//therefore, loop through and find the next to write
			bool [] section_not_written = new bool[len];
			//set all sections to not written
			for(i = 0;i < len;i++)
				section_not_written[i] = true;
			//for all sections
			for(i = 0;i < len;i++)
			{
				next = int.MaxValue;
				//index of next section to write
				k = 0;
				//find the next section at the smallest offset that is not written
				for(j = 0;j < len;j++)
				{
					sh = sectionheaders[j];
					if(sh.PointerToRawData < next && section_not_written[j])
					{
						next = sh.PointerToRawData;
						//save index of section to write
						k = j;
					}
				}
				//set section as writtem
				section_not_written[k] = false;
				//write section
				WriteSection(sectionheaders[k]);
			}
		}

		private void WriteSection(SectionHeader sh)
		{
			String name = sh.NameAsString;
			switch(name)
			{
				case ".text\0\0\0": 	WriteTextSection(sh);
							break;
				case ".rsrc\0\0\0": 	WriteRSRCSection(sh);
							break;
				case ".reloc\0\0":	WriteRELOCSection(sh);
							break;
				default:		throw new CLELWriterException("Unknown section name: "+name);
			}
		}

		private void WriteTextSection(SectionHeader text_header)
		{
			int total_size = text_header.SizeOfRawData;
			_rva = text_header.VirtualAddress;

			byte [] buffer = new byte[total_size];
			int i;
			for(i = 0;i < total_size;i++)
				buffer[i] = 0;

			CLELValidAssemblyTextGenerator text_writer = new CLELValidAssemblyTextGenerator(_asm,buffer,_writer.IsLittleEndian,_log,_rva);
			text_writer.WriteTextSection();
			
			_writer.Write(buffer,0,total_size);
			_file_offset += total_size;
		}

		private void WriteRSRCSection(SectionHeader rsrc_header)
		{
			//TODO
			byte[] rsrc = _asm.RSRCSection;
			_writer.Write(rsrc,0,rsrc.Length);
		}

		private void WriteRELOCSection(SectionHeader reloc_header)
		{
			//TODO
			byte[] reloc = _asm.RELOCSection;
			_writer.Write(reloc,0,reloc.Length);
		}
	}
}
